package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/astaxie/beego/orm"

	"mutu/misc"
	"mutu/misc/conf"
	"mutu/misc/log"
	"mutu/misc/typedef"
	"mutu/models"
)

type AuthManager struct {
}

const (
	DefHimg = "http://7xnl7r.com1.z0.glb.clouddn.com/avt.jpg"
)

var (
	AuthService = AuthManager{}
	UserMap     = typedef.Dict{}

	OauthQQAppId  = conf.GetString("OauthQQAppId")
	OauthQQAppKey = conf.GetString("OauthQQAppKey")
	OauthWBAppId  = conf.GetString("OauthWBAppId")
	OauthWBAppKey = conf.GetString("OauthWBAppKey")
	OauthWXAppId  = conf.GetString("OauthWXAppId")
	OauthWXAppKey = conf.GetString("OauthWXAppKey")
)

func (p AuthManager) CreateOauth(auth *models.Auth) error {
	if auth.OpenId == "" {
		auth.OpenId = misc.NewUUID()
	}
	_, err := models.Orm.Insert(auth)
	return err
}

func (p AuthManager) GetOauthUrl(domain string, way string) (typedef.Dict, error) {
	var l string
	if way == conf.F_Auth_QQ_S {
		l = fmt.Sprintf("https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=http://%s/auth/callback&scope=get_user_info&state=%s",
			OauthQQAppId, domain, way)
	} else if way == conf.F_Auth_WB_S {
		l = fmt.Sprintf("https:/api.weibo.com/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=http://%s/auth/callback&scope=get_user_info&state=%s",
			OauthWBAppId, domain, way)
	} else {
		return nil, errors.New("不支持的登录方式")
	}
	return typedef.Dict{
		"oauth_url": l,
	}, nil
}

//第三方登录回调地址
func (p AuthManager) CallBack(domain, way, code string) (typedef.Dict, error) {
	var oa OAuth
	if way == conf.F_Auth_QQ_S {
		oa = QQAuth{}
	} else if way == conf.F_Auth_WB_S {
		oa = WBAuth{}
	} else {
		log.Log.Error("登录回调出错，不支持的第三方：%s\n", way)
		return nil, errors.New("不支持的登录方式")
	}
	token, auth, err := oa.GetTokenAndOpenid(domain, code)
	if err != nil {
		log.Log.Error("登录回调出错，获取第三方token出错：%s\n", err)
		return nil, err
	}
	err = models.Orm.Read(auth, "Flag", "OpenId")
	var user *models.User
	if err != nil {
		if err == orm.ErrNoRows {
			user, err = oa.GetInfo(token, auth)
			if err != nil {
				log.Log.Error("登录回调出错，获取第三方用户信息出错：%s\n", err)
				return nil, err
			}
			if err = UserService.CreateUser(user); err != nil {
				log.Log.Error("登录回调出错，创建用户出错：%s\n", err)
				return nil, err
			}
			auth.UserId = user.Uuid
			if err = p.CreateOauth(auth); err != nil {
				log.Log.Error("登录回调出错，保存认证方式出错：%s\n", err)
				return nil, err
			}
		} else {
			log.Log.Error("登录回调出错，读取认证方式出错：%s\n", err)
			return nil, err
		}
	} else {
		user = &models.User{
			Uuid: auth.UserId,
		}
		if err = models.Orm.Read(user, "Uuid"); err != nil {
			log.Log.Error("登录回调出错，读取用户信息出错：%s\n", err)
			return nil, err
		}

	}

	ses, err := SessionService.CreateSession(user, auth)
	if err != nil {
		log.Log.Error("登录回调出错，创建session出错：%s\n", err)
		return nil, err
	}
	return typedef.Dict{
		"ticket": ses.Uuid,
		"live":   ses.Live,
	}, nil

}

func (p AuthManager) GetOauthInfo(ticket string) (typedef.Dict, error) {
	ses, err := SessionService.QuerySession(ticket)
	if err != nil {
		log.Log.Error("获取session出错：%s\n", err)
		return nil, err
	}

	return typedef.Dict{
		"ses": ses,
	}, nil

}

func (p AuthManager) PasswdLogin(email, passwd string) (typedef.Dict, error) {
	auth := &models.Auth{
		Flag:   conf.F_Auth_PW,
		Email:  email,
		Passwd: passwd,
	}

	err := models.Orm.Read(auth, "Flag", "Email")
	var user *models.User
	if err != nil {
		if err == orm.ErrNoRows {
			user = &models.User{
				Nick: strings.Split(email, "@")[0],
				Himg: DefHimg,
			}
			if err = UserService.CreateUser(user); err != nil {
				log.Log.Error("登录失败，创建用户出错：%s\n", err)
				return nil, err
			}
			auth.UserId = user.Uuid
			if err = p.CreateOauth(auth); err != nil {
				log.Log.Error("登录失败，保存认证方式出错：%s\n", err)
				return nil, err
			}
		} else {
			log.Log.Error("登录失败，读取认证方式出错：%s\n", err)
			return nil, err
		}
	} else {
		if auth.Passwd != passwd {
			return nil, errors.New("口令错误")
		}
		user = &models.User{
			Uuid: auth.UserId,
		}
		if err = models.Orm.Read(user, "Uuid"); err != nil {
			log.Log.Error("登录失败，读取用户信息出错：%s\n", err)
			return nil, err
		}
	}

	ses, err := SessionService.CreateSession(user, auth)
	if err != nil {
		log.Log.Error("登录失败，创建session出错：%s\n", err)
		return nil, err
	}
	return typedef.Dict{
		"ticket": ses.Uuid,
		"live":   ses.Live,
	}, nil

}

type OAuth interface {
	GetTokenAndOpenid(domain, code string) (string, *models.Auth, error)
	GetInfo(token string, auth *models.Auth) (*models.User, error)
}

type QQAuth struct{}

func parseQQError(str string) error {
	buf := str[10 : len(str)-3]
	d := typedef.Dict{}
	err := json.Unmarshal([]byte(buf), &d)
	if err != nil {
		return err
	}
	desc, ok := d["error_description"].(string)
	if !ok {
		desc = "qq互联出错"
	}
	return errors.New(desc)
}

func (p QQAuth) GetTokenAndOpenid(domain, code string) (string, *models.Auth, error) {
	l := fmt.Sprintf("https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&state=qq&redirect_uri=http://%s/auth/callback",
		OauthQQAppId, OauthQQAppKey, code, domain)
	response, err := http.Get(l)
	if err != nil {
		return "", nil, err
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", nil, err
	}
	s := fmt.Sprintf("/?%s", string(body))
	u, err := url.Parse(s)
	if err != nil {
		return "", nil, err
	}
	q := u.Query()
	token := q.Get("access_token")
	if len(token) == 0 {
		return "", nil, parseQQError(string(body))
	}

	l2 := fmt.Sprintf("https://graph.qq.com/oauth2.0/me?access_token=%s", token)
	response2, err := http.Get(l2)
	if err != nil {
		return "", nil, err
	}
	defer response2.Body.Close()
	body2, err := ioutil.ReadAll(response2.Body)
	if err != nil {
		return "", nil, err
	}
	buf := string(body2)
	if len(buf) <= 13 {
		return "", nil, errors.New("qq互联出错")
	}

	buf = buf[10 : len(buf)-3]
	d := typedef.Dict{}
	err = json.Unmarshal([]byte(buf), &d)
	if err != nil {
		return "", nil, err
	}
	openid, ok := d["openid"].(string)
	if !ok {
		return "", nil, parseQQError(string(body2))
	}
	auth := &models.Auth{
		Flag:   conf.F_Auth_QQ,
		OpenId: openid,
	}
	return token, auth, nil
}

func (p QQAuth) GetInfo(token string, auth *models.Auth) (*models.User, error) {
	l3 := fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s",
		token, OauthQQAppId, auth.OpenId)
	response3, err := http.Get(l3)
	if err != nil {
		return nil, err
	}
	defer response3.Body.Close()
	body3, err := ioutil.ReadAll(response3.Body)
	if err != nil {
		return nil, err
	}
	d2 := typedef.Dict{}
	err = json.Unmarshal(body3, &d2)
	if err != nil {
		return nil, err
	}
	auth.Ext = string(body3)
	fmt.Println(auth.Ext)
	user := &models.User{
		Nick: d2["nickname"].(string),
		Himg: d2["figureurl"].(string),
	}
	return user, nil
}

type WBAuth struct{}

func parseWBError(str string) error {
	d := typedef.Dict{}
	err := json.Unmarshal([]byte(str), &d)
	if err != nil {
		return err
	}
	desc, ok := d["error_description"].(string)
	if !ok {
		desc = "微博互联出错"
	}
	return errors.New(desc)
}

func (p WBAuth) GetTokenAndOpenid(domain, code string) (string, *models.Auth, error) {
	l := fmt.Sprintf("client_id=%s&client_secret=%s&grant_type=authorization_code&redirect_uri=http://%s/auth/callback&code=%s&state=wb",
		OauthWBAppId, OauthWBAppKey, domain, code)
	response, err := http.Post("https://api.weibo.com/oauth2/access_token", "application/x-www-form-urlencoded", strings.NewReader(l))
	if err != nil {
		return "", nil, err
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", nil, err
	}
	d3 := typedef.Dict{}
	err = json.Unmarshal(body, &d3)
	if err != nil {
		return "", nil, err
	}
	token, ok := d3["access_token"].(string)
	if !ok {
		return "", nil, parseWBError(string(body))
	}
	uid, ok := d3["uid"].(string)
	if !ok {
		return "", nil, errors.New("微博互联出错")
	}
	auth := &models.Auth{
		Flag:   conf.F_Auth_WB,
		OpenId: uid,
	}
	return token, auth, nil
}

func (p WBAuth) GetInfo(token string, auth *models.Auth) (*models.User, error) {
	l2 := fmt.Sprintf("https://api.weibo.com/2/users/show.json?access_token=%s&uid=%s", token, auth.OpenId)
	response2, err := http.Get(l2)
	if err != nil {
		return nil, err
	}
	defer response2.Body.Close()
	body2, err := ioutil.ReadAll(response2.Body)
	if err != nil {
		return nil, err
	}
	d2 := typedef.Dict{}
	err = json.Unmarshal(body2, &d2)
	if err != nil {
		return nil, err
	}
	auth.Ext = string(body2)
	fmt.Println(auth.Ext)
	user := &models.User{
		Nick: d2["name"].(string),
		Himg: d2["profile_image_url"].(string),
	}
	return user, nil
}
